#
# debian-cve-check.bbclass
#
# This class checks fixed CVEs in a Debian source package and adds them to
# the CVE_CHECK_WHITELIST before the cve_check runs.
#
# In order to use this class inherit cve-check and this class in the local.conf
# and run cve_check task.

DEBIAN_CVE_CHECK_DB_DIR ?= "${CVE_CHECK_DB_DIR}/DEBIAN"
DEBIAN_CODENAME ?= "${DISTRO_CODENAME}"

python debian_cve_check () {
    """
    Check CVE in Debian source
    """
    debian_src_uri = d.getVar("DEBIAN_SRC_URI", True)
    if debian_src_uri is None:
        bb.note("%s dosen't use debian source" % d.getVar("BPN"))
        return

    json_path = os.path.join(d.getVar("DEBIAN_CVE_CHECK_DB_DIR", True),"dst.json")
    dst_data = load_json(json_path)

    # get package name from DEBIAN_SRC_URI
    for _pkg_uri in debian_src_uri.split():
        if ".dsc" in _pkg_uri:
            _pkg_file_name = os.path.basename(_pkg_uri)
            pkgname = _pkg_file_name.split(";")[0].split("_")[0]
            break

    if pkgname not in dst_data.keys():
        bb.note("%s is not found in Debian Security Tracker." % pkgname)
        return

    deb_patched, deb_unpatched = deb_check_cves(d, dst_data[pkgname])

    bb.debug(2, "Whitelisted by DST:\n    %s" % "\n    ".join(deb_patched))
    d.appendVar("CVE_CHECK_WHITELIST", ' ' + ' '.join(deb_patched))
}

do_cve_check[prefuncs] += "debian_cve_check"

python update_dst () {
    """
    Update debian security tracker json file.
    """
    import urllib.request
    import shutil
    from datetime import datetime, date

    json_url = "https://security-tracker.debian.org/tracker/data/json"
    dist_path = os.path.join(d.getVar("DEBIAN_CVE_CHECK_DB_DIR", True),"dst.json")
    dist_dir = os.path.dirname(dist_path)

    if not os.path.isdir(dist_dir):
        os.mkdir(dist_dir)

    if os.path.isfile(dist_path):
        timestamp = datetime.fromtimestamp(os.path.getmtime(dist_path))
        if timestamp.date() == date.today():
            return

    with urllib.request.urlopen(json_url) as response, open(dist_path, 'wb') as f:
        shutil.copyfileobj(response, f)
    bb.debug(2, "DST database updated")
}

do_populate_cve_db[postfuncs] += "update_dst"

def load_json(path):
    """
    Load json file.
    """
    import json
    if not os.path.isfile(path):
        bb.error("%s dosen't exist" % path)
        return
    with open(path, 'r') as f:
        return json.load(f)

def deb_check_cves(d, pkg_data):
    """
    Judge cves patched or not.
    """
    patched = []
    unpatched = []

    if d.getVar("DPV_EPOCH") == "": 
        dpv = d.getVar("DPV")
    else:
        dpv = d.getVar("DPV_EPOCH") + ":" + d.getVar("DPV")
    
    debian_codename = d.getVar("DEBIAN_CODENAME", True)
    for cve in pkg_data.keys():
        cve_data = pkg_data[cve]["releases"][debian_codename]
        # if the status is "open" or "undetermined", the cve treat as unpatched
        if cve_data["status"] != "resolved":
            unpatched.append(cve)
            continue

        if compare_versions(dpv, cve_data["fixed_version"]):
            patched.append(cve)
        else:
            unpatched.append(cve)

    return patched, unpatched

def compare_versions(current_version, fixed_version):
    """
    If current_version >= fixed_version, return True.
    If not, return False.
    """
    import subprocess

    ret = subprocess.run(["/usr/bin/dpkg","--compare-versions", current_version,
                                        "ge",fixed_version]).returncode
    if ret == 0:
        return True
    else:
        return False
